<?php

/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @package log4php
 */

/**
 * The output of the LoggerXmlLayout consists of a series of log4php:event
 * elements.
 *
 *
 * Configurable parameters:
 * - {@link $locationInfo} - If set to true then the file name and line number
 * of the origin of the log statement will be included in output.
 * - {@link $log4jNamespace} - If set to true then log4j namespace will be used
 * instead of log4php namespace. This can be usefull when using log viewers
 * which can only parse the log4j namespace such as Apache Chainsaw.
 *
 * <p>It does not output a complete well-formed XML file.
 * The output is designed to be included as an external entity in a separate
 * file to form
 * a correct XML file.</p>
 *
 * Example:
 *
 * {@example ../../examples/php/layout_xml.php 19}<br>
 *
 * {@example ../../examples/resources/layout_xml.properties 18}<br>
 *
 * The above would print:
 *
 * <pre>
 * <log4php:eventSet xmlns:log4php="http://logging.apache.org/log4php/"
 * version="0.3" includesLocationInfo="true">
 * <log4php:event logger="root" level="INFO" thread="13802"
 * timestamp="1252456226491">
 * <log4php:message><![CDATA[Hello World!]]></log4php:message>
 * <log4php:locationInfo class="main" file="examples/php/layout_xml.php"
 * line="6" method="main" />
 * </log4php:event>
 * </log4php:eventSet>
 * </pre>
 *
 * @version $Revision: 1213283 $
 * @package log4php
 * @subpackage layouts
 */
class LoggerLayoutXml extends LoggerLayout
{

    const LOG4J_NS_PREFIX = 'log4j';

    const LOG4J_NS = 'http://jakarta.apache.org/log4j/';

    const LOG4PHP_NS_PREFIX = 'log4php';

    const LOG4PHP_NS = 'http://logging.apache.org/log4php/';

    const CDATA_START = '<![CDATA[';

    const CDATA_END = ']]>';

    const CDATA_PSEUDO_END = ']]&gt;';

    const CDATA_EMBEDDED_END = ']]>]]&gt;<![CDATA[';

    /**
     * If set to true then the file name and line number of the origin of the
     * log statement will be output.
     *
     * @var boolean
     */
    protected $locationInfo = true;

    /**
     * If set to true, log4j namespace will be used instead of the log4php
     * namespace.
     *
     * @var boolean
     */
    protected $log4jNamespace = false;

    /**
     * The namespace in use.
     */
    protected $namespace = self::LOG4PHP_NS;

    /**
     * The namespace prefix in use
     */
    protected $namespacePrefix = self::LOG4PHP_NS_PREFIX;

    public function activateOptions()
    {
        if ($this->getLog4jNamespace()) {
            $this->namespace = self::LOG4J_NS;
            $this->namespacePrefix = self::LOG4J_NS_PREFIX;
        } else {
            $this->namespace = self::LOG4PHP_NS;
            $this->namespacePrefix = self::LOG4PHP_NS_PREFIX;
        }
    }

    /**
     *
     * @return string
     */
    public function getHeader()
    {
        return "<{$this->namespacePrefix}:eventSet " . "xmlns:{$this->namespacePrefix}=\"{$this->namespace}\" " . "version=\"0.3\" " . "includesLocationInfo=\"" . ($this->getLocationInfo() ? "true" : "false") . "\"" . ">" . PHP_EOL;
    }

    /**
     * Formats a {@link LoggerLoggingEvent} in conformance with the log4php.dtd.
     *
     * @param LoggerLoggingEvent $event            
     * @return string
     */
    public function format(LoggerLoggingEvent $event)
    {
        $ns = $this->namespacePrefix;
        
        $loggerName = $event->getLoggerName();
        $timeStamp = number_format((float) ($event->getTimeStamp() * 1000), 0, '', '');
        $thread = $event->getThreadName();
        $level = $event->getLevel()->toString();
        
        $buf = "<$ns:event logger=\"{$loggerName}\" level=\"{$level}\" thread=\"{$thread}\" timestamp=\"{$timeStamp}\">" . PHP_EOL;
        $buf .= "<$ns:message>";
        $buf .= $this->encodeCDATA($event->getRenderedMessage());
        $buf .= "</$ns:message>" . PHP_EOL;
        
        $ndc = $event->getNDC();
        if (! empty($ndc)) {
            $buf .= "<$ns:NDC><![CDATA[";
            $buf .= $this->encodeCDATA($ndc);
            $buf .= "]]></$ns:NDC>" . PHP_EOL;
        }
        
        $mdcMap = $event->getMDCMap();
        if (! empty($mdcMap)) {
            $buf .= "<$ns:properties>" . PHP_EOL;
            foreach ($mdcMap as $name => $value) {
                $buf .= "<$ns:data name=\"$name\" value=\"$value\" />" . PHP_EOL;
            }
            $buf .= "</$ns:properties>" . PHP_EOL;
        }
        
        if ($this->getLocationInfo()) {
            $locationInfo = $event->getLocationInformation();
            $buf .= "<$ns:locationInfo " . "class=\"" . $locationInfo->getClassName() . "\" " . "file=\"" . htmlentities($locationInfo->getFileName(), ENT_QUOTES) . "\" " . "line=\"" . $locationInfo->getLineNumber() . "\" " . "method=\"" . $locationInfo->getMethodName() . "\" ";
            $buf .= "/>" . PHP_EOL;
        }
        
        $buf .= "</$ns:event>" . PHP_EOL;
        
        return $buf;
    }

    /**
     *
     * @return string
     */
    public function getFooter()
    {
        return "</{$this->namespacePrefix}:eventSet>" . PHP_EOL;
    }

    /**
     * Whether or not file name and line number will be included in the output.
     *
     * @return boolean
     */
    public function getLocationInfo()
    {
        return $this->locationInfo;
    }

    /**
     * The {@link $locationInfo} option takes a boolean value.
     * By default,
     * it is set to false which means there will be no location
     * information output by this layout. If the the option is set to
     * true, then the file name and line number of the statement at the
     * origin of the log statement will be output.
     */
    public function setLocationInfo($flag)
    {
        $this->setBoolean('locationInfo', $flag);
    }

    /**
     *
     * @return boolean
     */
    public function getLog4jNamespace()
    {
        return $this->log4jNamespace;
    }

    /**
     *
     * @param
     *            boolean
     */
    public function setLog4jNamespace($flag)
    {
        $this->setBoolean('log4jNamespace', $flag);
    }

    /**
     * Encases a string in CDATA tags, and escapes any existing CDATA end
     * tags already present in the string.
     *
     * @param string $string            
     */
    private function encodeCDATA($string)
    {
        $string = str_replace(self::CDATA_END, self::CDATA_EMBEDDED_END, $string);
        return self::CDATA_START . $string . self::CDATA_END;
    }
}

